#ifndef VARIANT_H
#define VARIANT_H

#include <cstdint>
#include <map>
#include <string>
#include <forward_list>

namespace sh
{
class Variant;

typedef std::map<std::string, Variant> ObjectValues;

enum ValueType
{
    nullValue = 0, ///< 'null' value
    intValue,      ///< signed integer value
    uintValue,     ///< unsigned integer value
    realValue,     ///< double value
    stringValue,   ///< UTF-8 string value
    booleanValue,  ///< bool value
    arrayValue,    ///< array value (ordered list)
    objectValue    ///< object value (collection of name/value pairs).
};

union ValueHolder
{
    int64_t int_;
    uint64_t uint_;
    double real_;
    bool bool_;
    char *string_; // if allocated_, ptr to { unsigned, char[] }.
    ObjectValues *map_;
};
struct Bits
{
    // Really a ValueType, but types should agree for bitfield packing.
    unsigned int value_type_: 8;
    // Unless allocated_, string_ must be null-terminated.
    unsigned int allocated_: 1;
};

class Variant
{
public:
    explicit Variant(ValueType type = nullValue);
    explicit Variant(int32_t value);
    explicit Variant(uint32_t value);
    explicit Variant(int64_t value);
    explicit Variant(uint64_t value);
    explicit Variant(double value);
    explicit Variant(const char *value); ///< Copy til first 0. (NULL causes to seg-fault.)
    explicit Variant(const char *begin, const char *end); ///< Copy all, incl zeroes.
    explicit Variant(const std::string &value);
    explicit Variant(bool value);
//    explicit Variant(const std::forward_list<Variant> &list);
    explicit Variant(const std::map<std::string, Variant> &value);
    Variant(std::nullptr_t ptr) = delete;
    Variant(const Variant &other);
    ~Variant();

    Variant &operator=(const Variant &other);
    Variant &operator=(Variant &&other);

    /// Swap everything.
    void swap(Variant &other);
    /// Swap values but leave comments and source offsets in place.
    void swapPayload(Variant &other);

    /// copy everything.
    void copy(const Variant &other);
    /// copy values but leave comments and source offsets in place.
    void copyPayload(const Variant &other);

    ValueType type() const;

    bool isNull() const;
    bool isBool() const;
    bool isInt() const;
    bool isInt64() const;
    bool isUInt() const;
    bool isUInt64() const;
    bool isIntegral() const;
    bool isDouble() const;
    bool isNumeric() const;
    bool isString() const;
    bool isArray() const;
    bool isObject() const;

    /// The `as<T>` and `is<T>` member function templates and specializations.
    template<typename T>
    T to() const;
    template<typename T>
    bool is() const;

    std::string toString(bool *ok = nullptr) const;
    int32_t toInt(bool *ok = nullptr) const;
    uint32_t toUInt(bool *ok = nullptr) const;
    int64_t toInt64(bool *ok = nullptr) const;
    uint64_t toUInt64(bool *ok = nullptr) const;
    float toFloat(bool *ok = nullptr) const;
    double toDouble(bool *ok = nullptr) const;
    bool toBool(bool *ok = nullptr) const;

    /// Number of values in array or object
    uint32_t size() const;
    /// \brief Return true if empty array, empty object, or null;
    /// otherwise, false.
    bool empty() const;

    /// Return !isNull()
    explicit operator bool() const;

    /// Remove all object members and array elements.
    /// \pre type() is arrayValue, objectValue, or nullValue
    /// \post type() is unchanged
    void clear();

private:
    void setType(ValueType v)
    {
        bits_.value_type_ = static_cast<unsigned char>(v);
    }
    void setIsAllocated(bool v)
    { bits_.allocated_ = v; }
    bool isAllocated() const
    { return bits_.allocated_; }
    void initBasic(ValueType type, bool allocated = false);

    void dupPayload(const Variant &other);
    void dupMeta(const Variant &other);
    void releasePayload();

private:
    ValueHolder value_;
    Bits bits_;
};

template<>
inline bool Variant::to<bool>() const
{ return toBool(); }
template<>
inline bool Variant::is<bool>() const
{ return isBool(); }

template<>
inline int32_t Variant::to<int32_t>() const
{ return toInt(); }
template<>
inline bool Variant::is<int32_t>() const
{ return isInt(); }

template<>
inline uint32_t Variant::to<uint32_t>() const
{ return toUInt(); }
template<>
inline bool Variant::is<uint32_t>() const
{ return isUInt(); }

template<>
inline double Variant::to<double>() const
{ return toDouble(); }
template<>
inline bool Variant::is<double>() const
{ return isDouble(); }

template<>
inline std::string Variant::to<std::string>() const
{ return toString(); }
template<>
inline bool Variant::is<std::string>() const
{ return isString(); }

template<>
inline float Variant::to<float>() const
{ return toFloat(); }
}

#endif //VARIANT_H
